// *****************************************************************************
// *****************************************************************************
// Copyright 2012 - 2015, Cadence Design Systems
//
// This  file  is  part  of  the  Cadence  LEF/DEF  Open   Source
// Distribution,  Product Version 5.8.
//
// Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//
//        http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
//    implied. See the License for the specific language governing
//    permissions and limitations under the License.
//
// For updates, support, or to become part of the LEF/DEF Community,
// check www.openeda.org for details.
//
//  $Author$
//  $Revision$
//  $Date$
//  $State:  $
// *****************************************************************************
// *****************************************************************************

// This program is the diffLef core program.  It has all the callback
// routines and write it out to a temporary file

#include <cstdio>
#include <cstring>
#ifndef WIN32
#include <unistd.h>
#endif /* not WIN32 */
#include "lefiDebug.hpp"
#include "lefrReader.hpp"
#include "lefwWriter.hpp"

char defaultName[128];
char defaultOut[128];
FILE* fout;
int printing = 0;  // Printing the output.
void* userData;

// TX_DIR:TRANSLATION ON

void dataError()
{
  fprintf(fout, "ERROR: returned user data is not correct!\n");
}

void checkType(lefrCallbackType_e c)
{
  if (c >= 0 && c <= lefrLibraryEndCbkType) {
    // OK
  } else {
    fprintf(fout, "ERROR: callback type is out of bounds!\n");
  }
}

// 05/24/2001 - Wanda da Rosa.  PCR 373170
// This function is added due to the rounding between machines are
// different.  For a 5, solaries will round down while hppa will roundup.
// This function will make sure it round up for all the machine
double chkNum(double num)
{
  long tempNum;
  if ((num > 1000004) || (num < -1000004)) {
    tempNum = (long) num;
    if ((tempNum % 5) == 0) {
      return num + 3;
    }
  }
  return num;
}

// Print Via
void lefVia(lefiVia* via, char* inName)
{
  int i, j;

  fprintf(fout, "%s %s", inName, via->name());
  if (via->hasDefault()) {
    fprintf(fout, " DEFAULT");
  }
  if (via->hasGenerated()) {
    fprintf(fout, " GENERATED");
  }
  if (via->hasTopOfStack()) {
    fprintf(fout, " TOPOFSTACKONLY");
  }
  if (via->hasForeign()) {
    fprintf(fout, " FOREIGN %s", via->foreign());
    if (via->hasForeignPnt()) {
      fprintf(
          fout, " ( %g %g )", chkNum(via->foreignX()), chkNum(via->foreignY()));
      if (via->hasForeignOrient()) {
        fprintf(fout, " %s", lefiOrientStr(via->foreignOrient()));
      }
    }
  }
  fprintf(fout, "\n");
  if (via->hasProperties()) {
    for (i = 0; i < via->numProperties(); i++) {
      fprintf(fout, "%s %s PROP", inName, via->name());
      fprintf(fout, " %s", via->propName(i));
      if (via->propIsNumber(i)) {
        fprintf(fout, " %g", chkNum(via->propNumber(i)));
      }
      if (via->propIsString(i)) {
        fprintf(fout, " %s", via->propValue(i));
      }
      fprintf(fout, "\n");
    }
  }
  if (via->hasViaRule()) {
    fprintf(fout,
            "%s %s %g %g %s %s %s %g %g %g %g %g %g",
            via->name(),
            via->viaRuleName(),
            via->xCutSize(),
            via->yCutSize(),
            via->botMetalLayer(),
            via->cutLayer(),
            via->topMetalLayer(),
            via->xCutSpacing(),
            via->yCutSpacing(),
            via->xBotEnc(),
            via->yBotEnc(),
            via->xTopEnc(),
            via->yTopEnc());
    if (via->hasRowCol()) {
      fprintf(fout, " %d %d", via->numCutRows(), via->numCutCols());
    }
    if (via->hasOrigin()) {
      fprintf(fout, " %g %g", via->xOffset(), via->yOffset());
    }
    if (via->hasOffset()) {
      fprintf(fout,
              " %g %g %g %g",
              via->xBotOffset(),
              via->yBotOffset(),
              via->xTopOffset(),
              via->yTopOffset());
    }
    if (via->hasCutPattern()) {
      fprintf(fout, " %s", via->cutPattern());
    }
    fprintf(fout, "\n");
  }
  if (via->hasResistance()) {
    fprintf(fout,
            "%s %s RESISTANCE %g\n",
            inName,
            via->name(),
            chkNum(via->resistance()));
  }
  if (via->numLayers() > 0) {
    for (i = 0; i < via->numLayers(); i++) {
      fprintf(fout, "%s %s LAYER %s\n", inName, via->name(), via->layerName(i));
      for (j = 0; j < via->numRects(i); j++) {
        if (via->rectColorMask(i, j)) {
          fprintf(fout,
                  "%s %s RECT MASK % d ( %g %g ) ( %g %g )\n",
                  inName,
                  via->name(),
                  via->rectColorMask(i, j),
                  chkNum(via->xl(i, j)),
                  chkNum(via->yl(i, j)),
                  chkNum(via->xh(i, j)),
                  chkNum(via->yh(i, j)));
        } else {
          fprintf(fout,
                  "%s %s RECT ( %g %g ) ( %g %g )\n",
                  inName,
                  via->name(),
                  chkNum(via->xl(i, j)),
                  chkNum(via->yl(i, j)),
                  chkNum(via->xh(i, j)),
                  chkNum(via->yh(i, j)));
        }
      }
      for (j = 0; j < via->numPolygons(i); j++) {
        struct lefiGeomPolygon poly;
        poly = via->getPolygon(i, j);
        if (via->polyColorMask(i, j)) {
          fprintf(fout,
                  "%s %s POLYGON MASK %d",
                  inName,
                  via->name(),
                  via->polyColorMask(i, j));
        } else {
          fprintf(fout, "%s %s POLYGON", inName, via->name());
        }
        for (int k = 0; k < poly.numPoints; k++) {
          fprintf(fout, " %g %g", poly.x[k], poly.y[k]);
        }
        fprintf(fout, "\n");
      }
    }
  }
  return;
}

// Print Spacing
void lefSpacing(lefiSpacing* spacing, char* inName)
{
  fprintf(fout,
          "%s SAMENET %s %s %g ",
          inName,
          spacing->name1(),
          spacing->name2(),
          chkNum(spacing->distance()));
  if (spacing->hasStack()) {
    fprintf(fout, " STACK");
  }
  fprintf(fout, "\n");
  return;
}

// Print Via Rule Layer
void lefViaRuleLayer(lefiViaRuleLayer* vLayer, char* viaName)
{
  fprintf(fout, "VIARULE %s LAYER %s", viaName, vLayer->name());
  if (vLayer->hasDirection()) {
    if (vLayer->isHorizontal()) {
      fprintf(fout, " DIRECTION HORIZONTAL");
    }
    if (vLayer->isVertical()) {
      fprintf(fout, " DIRECTION VERTICAL");
    }
  }
  if (vLayer->hasEnclosure()) {
    fprintf(fout,
            " ENCLOSURE %g %g\n",
            chkNum(vLayer->enclosureOverhang1()),
            chkNum(vLayer->enclosureOverhang2()));
  }
  if (vLayer->hasWidth()) {
    fprintf(fout,
            " WIDTH %g TO %g",
            chkNum(vLayer->widthMin()),
            chkNum(vLayer->widthMax()));
  }
  if (vLayer->hasResistance()) {
    fprintf(fout, " RESISTANCE %g", chkNum(vLayer->resistance()));
  }
  if (vLayer->hasOverhang()) {
    fprintf(fout, " OVERHANG %g", chkNum(vLayer->overhang()));
  }
  if (vLayer->hasMetalOverhang()) {
    fprintf(fout, " METALOVERHANG %g", chkNum(vLayer->metalOverhang()));
  }
  if (vLayer->hasSpacing()) {
    fprintf(fout,
            " SPACING %g BY %g",
            chkNum(vLayer->spacingStepX()),
            chkNum(vLayer->spacingStepY()));
  }
  if (vLayer->hasRect()) {
    fprintf(fout,
            " RECT ( %g %g ) ( %g %g )",
            chkNum(vLayer->xl()),
            chkNum(vLayer->yl()),
            chkNum(vLayer->xh()),
            chkNum(vLayer->yh()));
  }
  fprintf(fout, "\n");
  return;
}

// Print Geometry
void prtGeometry(lefiGeometries* geometry, char* inName)
{
  int numItems = geometry->numItems();
  int i, j;
  lefiGeomPath* path;
  lefiGeomPathIter* pathIter;
  lefiGeomRect* rect;
  lefiGeomRectIter* rectIter;
  lefiGeomPolygon* polygon;
  lefiGeomPolygonIter* polygonIter;
  lefiGeomVia* via;
  lefiGeomViaIter* viaIter;

  for (i = 0; i < numItems; i++) {
    switch (geometry->itemType(i)) {
      case lefiGeomClassE:
        fprintf(fout, "%s CLASS %s\n", inName, geometry->getClass(i));
        break;
      case lefiGeomLayerE:
        fprintf(fout, "%s LAYER %s\n", inName, geometry->getLayer(i));
        break;
      case lefiGeomLayerMinSpacingE:
        fprintf(fout,
                "%s SPACING %g\n",
                inName,
                chkNum(geometry->getLayerMinSpacing(i)));
        break;
      case lefiGeomLayerExceptPgNetE:
        fprintf(fout, "%s EXCEPTPGNET\n", inName);
        break;
      case lefiGeomLayerRuleWidthE:
        fprintf(fout,
                "%s DESIGNRULEWIDTH %g\n",
                inName,
                chkNum(geometry->getLayerRuleWidth(i)));
        break;
      case lefiGeomWidthE:
        fprintf(fout, "%s WIDTH %g\n", inName, chkNum(geometry->getWidth(i)));
        break;
      case lefiGeomPathE:
        path = geometry->getPath(i);
        if (path->colorMask != 0) {
          fprintf(fout, "%s PATH MASK %d ", inName, path->colorMask);
        } else {
          fprintf(fout, "%s PATH ", inName);
        }

        for (j = 0; j < path->numPoints; j++) {
          if (j + 1 == path->numPoints) {  // last one on the list
            fprintf(
                fout, " ( %g %g )\n", chkNum(path->x[j]), chkNum(path->y[j]));
          } else {
            fprintf(fout, " ( %g %g )", chkNum(path->x[j]), chkNum(path->y[j]));
          }
        }
        break;
      case lefiGeomPathIterE:
        pathIter = geometry->getPathIter(i);
        if (pathIter->colorMask != 0) {
          fprintf(
              fout, "%s PATH MASK %d ITERATED ", inName, pathIter->colorMask);
        } else {
          fprintf(fout, "%s PATH ITERATED ", inName);
        }
        for (j = 0; j < pathIter->numPoints; j++) {
          fprintf(fout,
                  " ( %g %g )",
                  chkNum(pathIter->x[j]),
                  chkNum(pathIter->y[j]));
        }
        fprintf(fout,
                " DO %g BY %g STEP %g %g\n",
                chkNum(pathIter->xStart),
                chkNum(pathIter->yStart),
                chkNum(pathIter->xStep),
                chkNum(pathIter->yStep));
        break;
      case lefiGeomRectE:
        rect = geometry->getRect(i);
        if (rect->colorMask != 0) {
          fprintf(fout,
                  "%s RECT MASK %d ( %g %g ) ( %g %g )\n",
                  inName,
                  rect->colorMask,
                  chkNum(rect->xl),
                  chkNum(rect->yl),
                  chkNum(rect->xh),
                  chkNum(rect->yh));
        } else {
          fprintf(fout,
                  "%s RECT ( %g %g ) ( %g %g )\n",
                  inName,
                  chkNum(rect->xl),
                  chkNum(rect->yl),
                  chkNum(rect->xh),
                  chkNum(rect->yh));
        }
        break;
      case lefiGeomRectIterE:
        rectIter = geometry->getRectIter(i);
        if (rectIter->colorMask != 0) {
          fprintf(fout,
                  "%s RECT MASK %d ITERATE ( %g %g ) ( %g %g )",
                  inName,
                  rectIter->colorMask,
                  chkNum(rectIter->xl),
                  chkNum(rectIter->yl),
                  chkNum(rectIter->xh),
                  chkNum(rectIter->yh));
        } else {
          fprintf(fout,
                  "%s RECT ITERATE ( %g %g ) ( %g %g )",
                  inName,
                  chkNum(rectIter->xl),
                  chkNum(rectIter->yl),
                  chkNum(rectIter->xh),
                  chkNum(rectIter->yh));
        }
        fprintf(fout,
                " DO %g BY %g STEP %g %g ;\n",
                chkNum(rectIter->xStart),
                chkNum(rectIter->yStart),
                chkNum(rectIter->xStep),
                chkNum(rectIter->yStep));
        break;
      case lefiGeomPolygonE:
        polygon = geometry->getPolygon(i);
        if (polygon->colorMask != 0) {
          fprintf(fout, "%s POLYGON MASK %d ", inName, polygon->colorMask);
        } else {
          fprintf(fout, "%s POLYGON ", inName);
        }
        for (j = 0; j < polygon->numPoints; j++) {
          if (j + 1 == polygon->numPoints) {  // last one on the list
            fprintf(fout,
                    " ( %g %g )\n",
                    chkNum(polygon->x[j]),
                    chkNum(polygon->y[j]));
          } else {
            fprintf(fout,
                    " ( %g %g )",
                    chkNum(polygon->x[j]),
                    chkNum(polygon->y[j]));
          }
        }
        break;
      case lefiGeomPolygonIterE:
        polygonIter = geometry->getPolygonIter(i);
        if (polygonIter->colorMask != 0) {
          fprintf(fout,
                  "%s POLYGON MASK %d ITERATE",
                  inName,
                  polygonIter->colorMask);
        } else {
          fprintf(fout, "%s POLYGON ITERATE", inName);
        }
        for (j = 0; j < polygonIter->numPoints; j++) {
          fprintf(fout,
                  " ( %g %g )",
                  chkNum(polygonIter->x[j]),
                  chkNum(polygonIter->y[j]));
        }
        fprintf(fout,
                " DO %g BY %g STEP %g %g\n",
                chkNum(polygonIter->xStart),
                chkNum(polygonIter->yStart),
                chkNum(polygonIter->xStep),
                chkNum(polygonIter->yStep));
        break;
      case lefiGeomViaE:
        via = geometry->getVia(i);
        if (via->topMaskNum != 0 || via->bottomMaskNum != 0
            || via->cutMaskNum != 0) {
          fprintf(fout,
                  "%s VIA MASK %d%d%d ( %g %g ) %s\n",
                  inName,
                  via->topMaskNum,
                  via->cutMaskNum,
                  via->bottomMaskNum,
                  chkNum(via->x),
                  chkNum(via->y),
                  via->name);
        } else {
          fprintf(fout,
                  "%s VIA ( %g %g ) %s\n",
                  inName,
                  chkNum(via->x),
                  chkNum(via->y),
                  via->name);
        }
        break;
      case lefiGeomViaIterE:
        viaIter = geometry->getViaIter(i);
        if (viaIter->topMaskNum != 0 || viaIter->cutMaskNum != 0
            || viaIter->bottomMaskNum != 0) {
          fprintf(fout,
                  "%s VIA ITERATE MASK %d%d%d ( %g %g ) %s",
                  inName,
                  viaIter->topMaskNum,
                  viaIter->cutMaskNum,
                  viaIter->bottomMaskNum,
                  chkNum(viaIter->x),
                  chkNum(viaIter->y),
                  viaIter->name);
        } else {
          fprintf(fout,
                  "%s VIA ITERATE ( %g %g ) %s",
                  inName,
                  chkNum(viaIter->x),
                  chkNum(viaIter->y),
                  viaIter->name);
        }
        fprintf(fout,
                " DO %g BY %g STEP %g %g\n",
                chkNum(viaIter->xStart),
                chkNum(viaIter->yStart),
                chkNum(viaIter->xStep),
                chkNum(viaIter->yStep));
        break;
      default:
        fprintf(fout, "BOGUS geometries type.\n");
        break;
    }
  }
}

// Antenna
int antennaCB(lefrCallbackType_e c, double value, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }

  switch (c) {
    case lefrAntennaInputCbkType:
      fprintf(fout, "ANTINPUTGATEAREA %g\n", chkNum(value));
      break;
    case lefrAntennaInoutCbkType:
      fprintf(fout, "ANTINOUTDIFFAREA %g\n", chkNum(value));
      break;
    case lefrAntennaOutputCbkType:
      fprintf(fout, "ANTOUTPUTDIFFAREA %g\n", chkNum(value));
      break;
    case lefrInputAntennaCbkType:
      fprintf(fout, "INPUTPINANTENNASIZE %g\n", chkNum(value));
      break;
    case lefrOutputAntennaCbkType:
      fprintf(fout, "OUTPUTPINANTENNASIZE %g\n", chkNum(value));
      break;
    case lefrInoutAntennaCbkType:
      fprintf(fout, "INOUTPINANTENNASIZE %g\n", chkNum(value));
      break;
    default:
      break;
  }
  return 0;
}

// Array
int arrayCB(lefrCallbackType_e c, lefiArray* a, lefiUserData ud)
{
  int i, j, defCaps;
  lefiSitePattern* pattern;
  lefiTrackPattern* track;
  lefiGcellPattern* gcell;

  checkType(c);
  if (ud != userData) {
    dataError();
  }

  if (a->numSitePattern() > 0) {
    for (i = 0; i < a->numSitePattern(); i++) {
      pattern = a->sitePattern(i);
      fprintf(fout,
              "ARRAY %s SITE %s %g %g %s DO %g BY %g STEP %g %g\n",
              a->name(),
              pattern->name(),
              chkNum(pattern->x()),
              chkNum(pattern->y()),
              lefiOrientStr(pattern->orient()),
              chkNum(pattern->xStart()),
              chkNum(pattern->yStart()),
              chkNum(pattern->xStep()),
              chkNum(pattern->yStep()));
    }
  }
  if (a->numCanPlace() > 0) {
    for (i = 0; i < a->numCanPlace(); i++) {
      pattern = a->canPlace(i);
      fprintf(fout,
              "ARRAY %s CANPLACE %s %g %g %s DO %g BY %g STEP %g %g\n",
              a->name(),
              pattern->name(),
              chkNum(pattern->x()),
              chkNum(pattern->y()),
              lefiOrientStr(pattern->orient()),
              chkNum(pattern->xStart()),
              chkNum(pattern->yStart()),
              chkNum(pattern->xStep()),
              chkNum(pattern->yStep()));
    }
  }
  if (a->numCannotOccupy() > 0) {
    for (i = 0; i < a->numCannotOccupy(); i++) {
      pattern = a->cannotOccupy(i);
      fprintf(fout,
              "ARRAY %s CANNOTOCCUPY %s %g %g %s DO %g BY %g STEP %g %g\n",
              a->name(),
              pattern->name(),
              chkNum(pattern->x()),
              chkNum(pattern->y()),
              lefiOrientStr(pattern->orient()),
              chkNum(pattern->xStart()),
              chkNum(pattern->yStart()),
              chkNum(pattern->xStep()),
              chkNum(pattern->yStep()));
    }
  }

  if (a->numTrack() > 0) {
    for (i = 0; i < a->numTrack(); i++) {
      track = a->track(i);
      // 11/22/99 - Wanda da Rosa, PCR 283781.  Merge the track with
      // layer names.  This way, if user split the layer into different
      // line, the diff program won't generate extra lines.
      if (track->numLayers() == 0) {
        fprintf(fout,
                "ARRAY %s TRACKS %s, %g DO %d STEP %g\n",
                a->name(),
                track->name(),
                chkNum(track->start()),
                track->numTracks(),
                chkNum(track->space()));
      } else {
        fprintf(fout,
                "ARRAY %s TRACKS %s, %g DO %d STEP %g ",
                a->name(),
                track->name(),
                chkNum(track->start()),
                track->numTracks(),
                chkNum(track->space()));
        for (j = 0; j < track->numLayers(); j++) {
          fprintf(fout, "%s ", track->layerName(j));
        }
        fprintf(fout, ";\n");
      }
    }
  }

  if (a->numGcell() > 0) {
    for (i = 0; i < a->numGcell(); i++) {
      gcell = a->gcell(i);
      fprintf(fout,
              "ARRAY %s GCELLGRID %s, %g DO %d STEP %g\n",
              a->name(),
              gcell->name(),
              chkNum(gcell->start()),
              gcell->numCRs(),
              chkNum(gcell->space()));
    }
  }

  if (a->numFloorPlans() > 0) {
    for (i = 0; i < a->numFloorPlans(); i++) {
      for (j = 0; j < a->numSites(i); j++) {
        pattern = a->site(i, j);
        fprintf(fout,
                "ARRAY %s FLOORPLAN %s %s %s %g %g %s DO %d BY %d STEP %g %g\n",
                a->name(),
                a->floorPlanName(i),
                a->siteType(i, j),
                pattern->name(),
                chkNum(pattern->x()),
                chkNum(pattern->y()),
                lefiOrientStr(pattern->orient()),
                (int) pattern->xStart(),
                (int) pattern->yStart(),
                chkNum(pattern->xStep()),
                chkNum(pattern->yStep()));
      }
    }
  }

  defCaps = a->numDefaultCaps();
  if (defCaps > 0) {
    for (i = 0; i < defCaps; i++) {
      fprintf(fout,
              "ARRAY %s DEFAULTCAP %d MINPINS %g WIRECAP %g\n",
              a->name(),
              defCaps,
              chkNum(a->defaultCapMinPins(i)),
              chkNum(a->defaultCap(i)));
    }
  }
  return 0;
}

// Busbitchar
int busBitCharsCB(lefrCallbackType_e c, const char* busBit, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  // use the lef writer to write out the data
  fprintf(fout, "BUSBITCHARS %s\n", busBit);
  return 0;
}

// CaseSensitive
int caseSensCB(lefrCallbackType_e c, int caseSense, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }

  if (caseSense) {
    fprintf(fout, "NAMESCASESENSITIVE ON\n");
  } else {
    fprintf(fout, "NAMESCASESENSITIVE OFF\n");
  }
  return 0;
}

// FixedMask
int fixedMaskCB(lefrCallbackType_e c, int fixedMask, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }

  if (fixedMask == 1) {
    fprintf(fout, "FIXEDMASK ;\n");
  }
  return 0;
}

// Crearance
int clearanceCB(lefrCallbackType_e c, const char* name, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }

  fprintf(fout, "CLEARANCEMEASURE %s\n", name);
  return 0;
}

// Crosstalk correcttable
int correctTableCB(lefrCallbackType_e c,
                   lefiCorrectionTable* table,
                   lefiUserData ud)
{
  int i, j, k, l;
  lefiCorrectionEdge* edge;
  lefiCorrectionResistance* resist;
  lefiCorrectionVictim* victim;

  checkType(c);
  if (ud != userData) {
    dataError();
  }

  fprintf(fout, "CROSSTALK CORRECTIONTABLE %d\n", table->num());
  for (i = 0; i < table->numEdges(); i++) {
    edge = table->edge(i);
    // resistances
    if (edge->numResistances()) {
      for (j = 0; j < edge->numResistances(); j++) {
        resist = edge->resistance(j);
        fprintf(fout,
                "CROSSTALK CORRECTIONTABLE %d RESISTANCE %g\n",
                table->num(),
                chkNum(resist->num(j)));
        // victims
        for (k = 0; k < resist->numVictims(); k++) {
          victim = resist->victim(k);
          if (victim->length() > 0) {
            fprintf(fout,
                    "CROSSTALK CORRECTIONTABLE %d RESISTANCE %g VICTIMLEN %g\n",
                    table->num(),
                    chkNum(resist->num(j)),
                    chkNum(victim->length()));
          }
          for (l = 0; l < victim->numCorrections(); l++) {
            fprintf(fout,
                    "CROSSTALK CORRECTIONTABLE %d RESISTANCE %g VICTIM GTH %g "
                    "CORRECTIONFACTOR %g\n",
                    table->num(),
                    chkNum(resist->num(j)),
                    chkNum(victim->length()),
                    chkNum(victim->correction(l)));
          }
        }
      }
    }
  }
  return 0;
}

// Dielectric
int dielectricCB(lefrCallbackType_e c, double dielectric, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }

  fprintf(fout, "DIELECTRIC %g\n", chkNum(dielectric));
  return 0;
}

// Divider
int dividerCB(lefrCallbackType_e c, const char* divideChar, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout, "DIVIDER %s\n", divideChar);
  return 0;
}

// Crosstalk edgeRate
int edgeRateCB(lefrCallbackType_e c, double rate, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout, "CROSSTALK EDGERATESCALEFACTOR %g\n", chkNum(rate));
  return 0;
}

// Callback routine for edgeratethreshold1
int edgeRate1CB(lefrCallbackType_e c, double rate, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout, "CROSSTALK EDGERATETHRESHOLD1 %g\n", chkNum(rate));
  return 0;
}

// Callback routine for edgeratethreshold2
int edgeRate2CB(lefrCallbackType_e c, double rate, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout, "CROSSTALK EDGERATETHRESHOLD2 %g\n", chkNum(rate));
  return 0;
}

// InputAntenna
int inputAntCB(lefrCallbackType_e c, double antenna, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout, "INPUTINANTENNASIZE %g\n", chkNum(antenna));
  return 0;
}

// OutputAntenna
int outputAntCB(lefrCallbackType_e c, double antenna, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout, "OUTPUTINANTENNASIZE %g\n", chkNum(antenna));
  return 0;
}

// InOutAntenna
int inoutAntCB(lefrCallbackType_e c, double antenna, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout, "INOUTPUTINANTENNASIZE %g\n", chkNum(antenna));
  return 0;
}

// Irdrop
int irdropCB(lefrCallbackType_e c, lefiIRDrop* irdrop, lefiUserData ud)
{
  int i;
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout, "IRDROP TABLE %s ", irdrop->name());
  for (i = 0; i < irdrop->numValues(); i++) {
    fprintf(
        fout, "%g %g ", chkNum(irdrop->value1(i)), chkNum(irdrop->value2(i)));
  }
  fprintf(fout, "\n");
  return 0;
}

// Layer
int layerCB(lefrCallbackType_e c, lefiLayer* layer, lefiUserData ud)
{
  int i, j, k;
  double *widths, *current;
  lefiLayerDensity* density;
  char pType;
  int numPoints, propNum;
  lefiAntennaPWL* pwl;
  int needHeading;
  int numMinCut, numMinenclosed;
  lefiSpacingTable* spTable;
  lefiInfluence* influence;
  lefiParallel* parallel;
  lefiTwoWidths* twoWidths;
  lefiAntennaModel* aModel;
  lefiOrthogonal* ortho;

  checkType(c);
  if (ud != userData) {
    dataError();
  }
  if (layer->hasType()) {
    fprintf(fout, "LAYER %s TYPE %s\n", layer->name(), layer->type());
  }
  if (layer->hasMask()) {
    fprintf(fout, "LAYER %s MASK %d\n", layer->name(), layer->mask());
  }
  if (layer->hasPitch()) {
    fprintf(fout, "LAYER %s PITCH %g\n", layer->name(), chkNum(layer->pitch()));
  } else if (layer->hasXYPitch()) {
    fprintf(fout,
            "LAYER %s PITCH %g %g\n",
            layer->name(),
            chkNum(layer->pitchX()),
            chkNum(layer->pitchY()));
  }
  if (layer->hasOffset()) {
    fprintf(
        fout, "LAYER %s OFFSET %g\n", layer->name(), chkNum(layer->offset()));
  } else if (layer->hasXYOffset()) {
    fprintf(fout,
            "LAYER %s OFFSET %g %g\n",
            layer->name(),
            chkNum(layer->offsetX()),
            chkNum(layer->offsetY()));
  }
  if (layer->hasDiagPitch()) {
    fprintf(fout,
            "LAYER %s DIAGPITCH %g\n",
            layer->name(),
            chkNum(layer->diagPitch()));
  } else if (layer->hasXYDiagPitch()) {
    fprintf(fout,
            "LAYER %s DIAGPITCH %g %g\n",
            layer->name(),
            chkNum(layer->diagPitchX()),
            chkNum(layer->diagPitchY()));
  }
  if (layer->hasDiagWidth()) {
    fprintf(fout,
            "LAYER %s DIAGWIDTH %g\n",
            layer->name(),
            chkNum(layer->diagWidth()));
  }
  if (layer->hasDiagSpacing()) {
    fprintf(fout,
            "LAYER %s DIAGSPACING %g\n",
            layer->name(),
            chkNum(layer->diagSpacing()));
  }
  if (layer->hasDiagMinEdgeLength()) {
    fprintf(fout,
            "LAYER %s DIAGMINEDGELENGTH %g\n",
            layer->name(),
            chkNum(layer->diagMinEdgeLength()));
  }
  if (layer->hasWidth()) {
    fprintf(fout, "LAYER %s WIDTH %g\n", layer->name(), chkNum(layer->width()));
  }
  if (layer->hasArea()) {
    fprintf(fout, "LAYER %s AREA %g\n", layer->name(), chkNum(layer->area()));
  }
  if (layer->numMinSize()) {
    fprintf(fout, "LAYER %s MINSIZE", layer->name());
    for (i = 0; i < layer->numMinSize(); i++) {
      fprintf(fout, " %g %g", layer->minSizeWidth(i), layer->minSizeLength(i));
    }
    fprintf(fout, "\n");
  }
  if (layer->hasSlotWireWidth()) {
    fprintf(fout,
            "LAYER %s SLOTWIREWIDTH %g\n",
            layer->name(),
            chkNum(layer->slotWireWidth()));
  }
  if (layer->hasSlotWireLength()) {
    fprintf(fout,
            "LAYER %s SLOTWIRELENGTH %g\n",
            layer->name(),
            chkNum(layer->slotWireLength()));
  }
  if (layer->hasSlotWidth()) {
    fprintf(fout,
            "LAYER %s SLOTWIDTH %g\n",
            layer->name(),
            chkNum(layer->slotWidth()));
  }
  if (layer->hasSlotLength()) {
    fprintf(fout,
            "LAYER %s SLOTLENGTH %g\n",
            layer->name(),
            chkNum(layer->slotLength()));
  }
  if (layer->hasMaxAdjacentSlotSpacing()) {
    fprintf(fout,
            "LAYER %s MAXADJACENTSLOTSPACING %g\n",
            layer->name(),
            chkNum(layer->maxAdjacentSlotSpacing()));
  }
  if (layer->hasMaxCoaxialSlotSpacing()) {
    fprintf(fout,
            "LAYER %s MAXCOAXIALSLOTSPACING %g\n",
            layer->name(),
            chkNum(layer->maxCoaxialSlotSpacing()));
  }
  if (layer->hasMaxEdgeSlotSpacing()) {
    fprintf(fout,
            "LAYER %s MAXEDGESLOTSPACING %g\n",
            layer->name(),
            chkNum(layer->maxEdgeSlotSpacing()));
  }
  if (layer->hasArraySpacing()) {
    fprintf(fout, "LAYER %s ARRAYSPACING", layer->name());
    if (layer->hasLongArray()) {
      fprintf(fout, " LONGARRAY");
    }
    if (layer->hasViaWidth()) {
      fprintf(fout, " WIDTH %g", chkNum(layer->viaWidth()));
    }
    fprintf(fout, " CUTSPACING %g", chkNum(layer->cutSpacing()));
    fprintf(fout, "\n");
    for (i = 1; i < layer->numArrayCuts(); i++) {
      fprintf(fout,
              "LAYER %s ARRAYCUTS %i SPACING %g\n",
              layer->name(),
              layer->arrayCuts(i),
              layer->arraySpacing(i));
    }
  }
  if (layer->hasSplitWireWidth()) {
    fprintf(fout,
            "LAYER %s SPLITWIREWIDTH %g\n",
            layer->name(),
            chkNum(layer->splitWireWidth()));
  }
  if (layer->hasMinimumDensity()) {
    fprintf(fout,
            "LAYER %s MINIMUMDENSITY %g\n",
            layer->name(),
            chkNum(layer->minimumDensity()));
  }
  if (layer->hasMaximumDensity()) {
    fprintf(fout,
            "LAYER %s MAXIMUMDENSITY %g\n",
            layer->name(),
            chkNum(layer->maximumDensity()));
  }
  if (layer->hasDensityCheckWindow()) {
    fprintf(fout,
            "LAYER %s DENSITYCHECKWINDOW %g %g\n",
            layer->name(),
            chkNum(layer->densityCheckWindowLength()),
            chkNum(layer->densityCheckWindowWidth()));
  }
  if (layer->hasDensityCheckStep()) {
    fprintf(fout,
            "LAYER %s DENSITYCHECKSTEP %g\n",
            layer->name(),
            chkNum(layer->densityCheckStep()));
  }
  if (layer->hasFillActiveSpacing()) {
    fprintf(fout,
            "LAYER %s FILLACTIVESPACING %g\n",
            layer->name(),
            chkNum(layer->fillActiveSpacing()));
  }
  numMinCut = layer->numMinimumcut();  // 5.4.1
  if (numMinCut > 0) {
    for (i = 0; i < numMinCut; i++) {
      fprintf(fout,
              "LAYER %s MINIMUMCUT %d WIDTH %g",
              layer->name(),
              layer->minimumcut(i),
              layer->minimumcutWidth(i));
      if (layer->hasMinimumcutWithin(i)) {
        fprintf(fout, " WITHIN %g", layer->minimumcutWithin(i));
      }
      if (layer->hasMinimumcutConnection(i)) {
        fprintf(fout, " %s", layer->minimumcutConnection(i));
      }
      if (layer->hasMinimumcutNumCuts(i)) {
        fprintf(fout,
                " LENGTH %g WITHIN %g",
                layer->minimumcutLength(i),
                layer->minimumcutDistance(i));
      }
      fprintf(fout, "\n");
    }
  }
  if (layer->hasMaxwidth()) {  // 5.4.1
    fprintf(fout, "LAYER %s MAXWIDTH %g\n", layer->name(), layer->maxwidth());
  }
  if (layer->hasMinwidth()) {  // 5.5
    fprintf(fout, "LAYER %s MINWIDTH %g\n", layer->name(), layer->minwidth());
  }
  numMinenclosed = layer->numMinenclosedarea();
  if (numMinenclosed > 0) {
    for (i = 0; i < numMinenclosed; i++) {  // 5.5, made it multiples
      fprintf(fout,
              "LAYER %s MINENCLOSEDAREA %g",
              layer->name(),
              layer->minenclosedarea(i));
      if (layer->hasMinenclosedareaWidth(i)) {
        fprintf(
            fout, " MINENCLOSEDAREAWIDTH %g", layer->minenclosedareaWidth(i));
      }
      fprintf(fout, "\n");
    }
  }
  if (layer->hasMinstep()) {  // 5.4.1 & 5.6
    for (i = 0; i < layer->numMinstep(); i++) {
      fprintf(fout, "LAYER %s MINSTEP %g", layer->name(), layer->minstep(i));
      if (layer->hasMinstepType(i)) {
        fprintf(fout, " %s", layer->minstepType(i));
      }
      if (layer->hasMinstepLengthsum(i)) {
        fprintf(fout, " LENGTHSUM %g", layer->minstepLengthsum(i));
      }
      if (layer->hasMinstepMaxedges(i)) {
        fprintf(fout, " MAXEDGES %d", layer->minstepMaxedges(i));
      }
      fprintf(fout, "\n");
    }
  }
  if (layer->hasProtrusion()) {  // 5.4.1
    fprintf(fout,
            "LAYER %s PROTRUSIONWIDTH %g LENGTH %g WIDTH %g\n",
            layer->name(),
            layer->protrusionWidth1(),
            layer->protrusionLength(),
            layer->protrusionWidth2());
  }
  if (layer->hasSpacingNumber()) {
    for (i = 0; i < layer->numSpacing(); i++) {
      fprintf(fout,
              "LAYER %s SPACING %g",
              layer->name(),
              chkNum(layer->spacing(i)));
      if (layer->spacingName(i)) {
        fprintf(fout, " LAYER %s", layer->spacingName(i));
      }
      if (layer->hasSpacingLayerStack(i)) {
        fprintf(fout, " STACK");
      }
      fprintf(fout, "\n");
      if (layer->hasSpacingAdjacent(i)) {
        fprintf(fout,
                "LAYER %s ADJACENTCUTS %d WITHIN %g\n",
                layer->name(),
                layer->spacingAdjacentCuts(i),
                chkNum(layer->spacingAdjacentWithin(i)));
      }
      if (layer->hasSpacingAdjacentExcept(i)) {
        fprintf(fout, "LAYER %s EXCEPTSAMEPGNET\n", layer->name());
      }
      if (layer->hasSpacingCenterToCenter(i)) {
        fprintf(fout, "LAYER %s CENTERTOCENTER\n", layer->name());
      }
      if (layer->hasSpacingSamenet(i)) {
        fprintf(fout, "LAYER %s SAMENET", layer->name());
        if (layer->hasSpacingSamenetPGonly(i)) {
          fprintf(fout, " PGONLY\n");
        } else {
          fprintf(fout, "\n");
        }
      }
      if (layer->hasSpacingArea(i)) {
        fprintf(fout,
                "LAYER %s AREA %g\n",
                layer->name(),
                chkNum(layer->spacingArea(i)));
      }
      if (layer->hasSpacingRange(i)) {
        fprintf(fout,
                "LAYER %s RANGE %g %g",
                layer->name(),
                chkNum(layer->spacingRangeMin(i)),
                chkNum(layer->spacingRangeMax(i)));
        if (layer->hasSpacingRangeUseLengthThreshold(i)) {
          fprintf(fout, "USELENGTHTHRESHOLD ");
        } else if (layer->hasSpacingRangeInfluence(i)) {
          fprintf(fout, "INFLUENCE %g ", layer->spacingRangeInfluence(i));
          if (layer->hasSpacingRangeInfluenceRange(i)) {
            fprintf(fout,
                    "RANGE %g %g ",
                    layer->spacingRangeInfluenceMin(i),
                    layer->spacingRangeInfluenceMax(i));
          }
        } else if (layer->hasSpacingRangeRange(i)) {
          fprintf(fout,
                  "RANGE %g %g ",
                  layer->spacingRangeRangeMin(i),
                  layer->spacingRangeRangeMax(i));
        }
        fprintf(fout, "\n");
      } else if (layer->hasSpacingLengthThreshold(i)) {
        fprintf(fout,
                "LAYER %s LENGTHTHRESHOLD %g\n",
                layer->name(),
                chkNum(layer->spacingLengthThreshold(i)));
        if (layer->hasSpacingLengthThresholdRange(i)) {
          fprintf(fout,
                  "RANGE %g %g",
                  layer->spacingLengthThresholdRangeMin(i),
                  layer->spacingLengthThresholdRangeMax(i));
        }
        fprintf(fout, "\n");
      } else if (layer->hasSpacingNotchLength(i)) {
        fprintf(fout,
                "LAYER %s NOTCHLENGTH %g\n",
                layer->name(),
                chkNum(layer->spacingNotchLength(i)));
      } else if (layer->hasSpacingEndOfNotchWidth(i)) {
        fprintf(fout,
                "LAYER %s ENDOFNOTCHWIDTH %g NOTCHSPACING %g, NOTCHLENGTH %g\n",
                layer->name(),
                chkNum(layer->spacingEndOfNotchWidth(i)),
                chkNum(layer->spacingEndOfNotchSpacing(i)),
                chkNum(layer->spacingEndOfNotchLength(i)));
      }
      if (layer->hasSpacingParallelOverlap(i)) {
        fprintf(fout, "LAYER %s PARALLELOVERLAP\n", layer->name());
      }
      if (layer->hasSpacingEndOfLine(i)) {
        fprintf(fout,
                "LAYER %s ENDOFLINE %g WITHIN %g ",
                layer->name(),
                chkNum(layer->spacingEolWidth(i)),
                chkNum(layer->spacingEolWithin(i)));
        if (layer->hasSpacingParellelEdge(i)) {
          fprintf(fout,
                  "PARALLELEDGE %g WITHIN %g ",
                  chkNum(layer->spacingParSpace(i)),
                  chkNum(layer->spacingParWithin(i)));
          if (layer->hasSpacingTwoEdges(i)) {
            fprintf(fout, "TWOEDGES ");
          }
        }
        fprintf(fout, "\n");
      }
    }
  }
  if (layer->hasSpacingTableOrtho()) {
    fprintf(fout, "LAYER %s SPACINGTABLE ORTHOGONAL\n", layer->name());
    ortho = layer->orthogonal();
    for (i = 0; i < ortho->numOrthogonal(); i++) {
      fprintf(fout,
              "LAYER %s SPACINGTABLE ORTHOGONAL WITHIN %g SPACING %g\n",
              layer->name(),
              ortho->cutWithin(i),
              ortho->orthoSpacing(i));
    }
    fprintf(fout, ";\n");
  }
  if (layer->numEnclosure() > 0) {
    fprintf(fout, "LAYER %s ENCLOSURE", layer->name());
    for (i = 0; i < layer->numEnclosure(); i++) {
      if (layer->hasEnclosureRule(i)) {
        fprintf(fout, " %s", layer->enclosureRule(i));
      }
      fprintf(fout,
              " %g %g",
              layer->enclosureOverhang1(i),
              layer->enclosureOverhang2(i));
      if (layer->hasEnclosureWidth(i)) {
        fprintf(fout, " WIDTH %g", layer->enclosureMinWidth(i));
      }
      if (layer->hasEnclosureExceptExtraCut(i)) {
        fprintf(fout, " EXCEPTEXTRACUT %g", layer->enclosureExceptExtraCut(i));
      }
      if (layer->hasEnclosureMinLength(i)) {
        fprintf(fout, " LENGTH %g", layer->enclosureMinLength(i));
      }
      fprintf(fout, "\n");
    }
  }
  if (layer->numPreferEnclosure() > 0) {
    fprintf(fout, "LAYER %s PREFERENCLOSURE", layer->name());
    for (i = 0; i < layer->numPreferEnclosure(); i++) {
      if (layer->hasPreferEnclosureRule(i)) {
        fprintf(fout, "%s ", layer->preferEnclosureRule(i));
      }
      fprintf(fout,
              "%g %g ",
              layer->preferEnclosureOverhang1(i),
              layer->preferEnclosureOverhang2(i));
      if (layer->hasPreferEnclosureWidth(i)) {
        fprintf(fout, "WIDTH %g ", layer->preferEnclosureMinWidth(i));
      }
      fprintf(fout, "\n");
    }
  }
  if (layer->hasResistancePerCut()) {
    fprintf(fout,
            "LAYER %s RESISTANCE %g\n",
            layer->name(),
            chkNum(layer->resistancePerCut()));
  }
  if (layer->hasCurrentDensityPoint()) {
    fprintf(fout,
            "LAYER %s CURRENTDEN %g\n",
            layer->name(),
            chkNum(layer->currentDensityPoint()));
  }
  if (layer->hasCurrentDensityArray()) {
    layer->currentDensityArray(&numPoints, &widths, &current);
    for (i = 0; i < numPoints; i++) {
      fprintf(fout,
              "LAYER %s CURRENTDEN ( %g %g )\n",
              layer->name(),
              chkNum(widths[i]),
              chkNum(current[i]));
    }
  }
  if (layer->hasDirection()) {
    fprintf(fout, "LAYER %s DIRECTION %s\n", layer->name(), layer->direction());
  }
  if (layer->hasResistance()) {
    fprintf(fout,
            "LAYER %s RESISTANCE RPERSQ %g\n",
            layer->name(),
            chkNum(layer->resistance()));
  }
  if (layer->hasCapacitance()) {
    fprintf(fout,
            "LAYER %s CAPACITANCE CPERSQDIST %g\n",
            layer->name(),
            chkNum(layer->capacitance()));
  }
  if (layer->hasHeight()) {
    fprintf(
        fout, "LAYER %s HEIGHT %g\n", layer->name(), chkNum(layer->height()));
  }
  if (layer->hasThickness()) {
    fprintf(fout,
            "LAYER %s THICKNESS %g\n",
            layer->name(),
            chkNum(layer->thickness()));
  }
  if (layer->hasWireExtension()) {
    fprintf(fout,
            "LAYER %s WIREEXTENSION %g\n",
            layer->name(),
            chkNum(layer->wireExtension()));
  }
  if (layer->hasShrinkage()) {
    fprintf(fout,
            "LAYER %s SHRINKAGE %g\n",
            layer->name(),
            chkNum(layer->shrinkage()));
  }
  if (layer->hasCapMultiplier()) {
    fprintf(fout,
            "LAYER %s CAPMULTIPLIER %g\n",
            layer->name(),
            chkNum(layer->capMultiplier()));
  }
  if (layer->hasEdgeCap()) {
    fprintf(fout,
            "LAYER %s EDGECAPACITANCE %g\n",
            layer->name(),
            chkNum(layer->edgeCap()));
  }
  if (layer->hasAntennaArea()) {
    fprintf(fout,
            "LAYER %s ANTAREAFACTOR %g\n",
            layer->name(),
            chkNum(layer->antennaArea()));
  }
  if (layer->hasAntennaLength()) {
    fprintf(fout,
            "LAYER %s ANTLENGTHFACTOR %g\n",
            layer->name(),
            chkNum(layer->antennaLength()));
  }
  for (j = 0; j < layer->numAntennaModel(); j++) {  // 5.5
    aModel = layer->antennaModel(j);

    if (aModel->hasAntennaAreaRatio()) {
      fprintf(fout,
              "LAYER %s %s ANTAREARATIO %g\n",
              layer->name(),
              aModel->antennaOxide(),
              chkNum(aModel->antennaAreaRatio()));
    }
    if (aModel->hasAntennaDiffAreaRatio()) {
      fprintf(fout,
              "LAYER %s %s ANTDIFFAREARATIO %g\n",
              layer->name(),
              aModel->antennaOxide(),
              chkNum(aModel->antennaDiffAreaRatio()));
    } else if (aModel->hasAntennaDiffAreaRatioPWL()) {
      pwl = aModel->antennaDiffAreaRatioPWL();
      fprintf(fout,
              "LAYER %s %s ANTDIFFAREARATIO PWL",
              layer->name(),
              aModel->antennaOxide());
      for (i = 0; i < pwl->numPWL(); i++) {
        fprintf(fout,
                " %g %g",
                chkNum(pwl->PWLdiffusion(i)),
                chkNum(pwl->PWLratio(i)));
      }
      fprintf(fout, "\n");
    }
    if (aModel->hasAntennaCumAreaRatio()) {
      fprintf(fout,
              "LAYER %s %s ANTCUMAREARATIO %g\n",
              layer->name(),
              aModel->antennaOxide(),
              chkNum(aModel->antennaCumAreaRatio()));
    }
    if (aModel->hasAntennaCumDiffAreaRatio()) {
      fprintf(fout,
              "LAYER %s %s ANTCUMDIFFAREARATIO %g\n",
              layer->name(),
              aModel->antennaOxide(),
              chkNum(aModel->antennaCumDiffAreaRatio()));
    } else if (aModel->hasAntennaCumDiffAreaRatioPWL()) {
      pwl = aModel->antennaCumDiffAreaRatioPWL();
      fprintf(fout,
              "LAYER %s %s ANTCUMDIFFAREARATIO PWL",
              layer->name(),
              aModel->antennaOxide());
      for (i = 0; i < pwl->numPWL(); i++) {
        fprintf(fout,
                " %g %g",
                chkNum(pwl->PWLdiffusion(i)),
                chkNum(pwl->PWLratio(i)));
      }
      fprintf(fout, "\n");
    }
    if (aModel->hasAntennaAreaFactor()) {
      fprintf(fout,
              "LAYER %s %s ANTAREAFACTOR %g",
              layer->name(),
              aModel->antennaOxide(),
              chkNum(aModel->antennaAreaFactor()));
      if (aModel->hasAntennaAreaFactorDUO()) {
        fprintf(fout, "  DIFFUSEONLY");
      }
      fprintf(fout, "\n");
    }
    if (aModel->hasAntennaSideAreaRatio()) {
      fprintf(fout,
              "LAYER %s %s ANTSIDEAREARATIO %g\n",
              layer->name(),
              aModel->antennaOxide(),
              chkNum(aModel->antennaSideAreaRatio()));
    }
    if (aModel->hasAntennaDiffSideAreaRatio()) {
      fprintf(fout,
              "LAYER %s %s ANTDIFFSIDEAREARATIO %g\n",
              layer->name(),
              aModel->antennaOxide(),
              chkNum(aModel->antennaDiffSideAreaRatio()));
    } else if (aModel->hasAntennaDiffSideAreaRatioPWL()) {
      pwl = aModel->antennaDiffSideAreaRatioPWL();
      fprintf(fout,
              "LAYER %s %s ANTDIFFSIDEAREARATIO PWL",
              layer->name(),
              aModel->antennaOxide());
      for (i = 0; i < pwl->numPWL(); i++) {
        fprintf(fout,
                " %g %g",
                chkNum(pwl->PWLdiffusion(i)),
                chkNum(pwl->PWLratio(i)));
      }
      fprintf(fout, "\n");
    }
    if (aModel->hasAntennaCumSideAreaRatio()) {
      fprintf(fout,
              "LAYER %s %s ANTCUMSIDEAREARATIO %g\n",
              layer->name(),
              aModel->antennaOxide(),
              chkNum(aModel->antennaCumSideAreaRatio()));
    }
    if (aModel->hasAntennaCumDiffSideAreaRatio()) {
      fprintf(fout,
              "LAYER %s %s ANTCUMDIFFSIDEAREARATIO %g\n",
              layer->name(),
              aModel->antennaOxide(),
              chkNum(aModel->antennaCumDiffSideAreaRatio()));
    } else if (aModel->hasAntennaCumDiffSideAreaRatioPWL()) {
      pwl = aModel->antennaCumDiffSideAreaRatioPWL();
      fprintf(fout,
              "LAYER %s %s ANTCUMDIFFSIDEAREARATIO PWL",
              layer->name(),
              aModel->antennaOxide());
      for (i = 0; i < pwl->numPWL(); i++) {
        fprintf(fout,
                "%g %g ",
                chkNum(pwl->PWLdiffusion(i)),
                chkNum(pwl->PWLratio(i)));
      }
      fprintf(fout, "\n");
    }
    if (aModel->hasAntennaSideAreaFactor()) {
      fprintf(fout,
              "  ANTSIDEAREAFACTOR %g ",
              chkNum(aModel->antennaSideAreaFactor()));
      if (aModel->hasAntennaSideAreaFactorDUO()) {
        fprintf(fout, "  DIFFUSEONLY ");
      }
      fprintf(fout, "\n");
    }
  }

  if (layer->numAccurrentDensity()) {
    for (i = 0; i < layer->numAccurrentDensity(); i++) {
      density = layer->accurrent(i);
      fprintf(
          fout, "LAYER %s ACCURRENTDENSITY %s", layer->name(), density->type());
      needHeading = 0;
      if (density->hasOneEntry()) {
        fprintf(fout, " %g\n", chkNum(density->oneEntry()));
      } else {
        if (density->numFrequency()) {
          /*
                          if (needHeading) {
                             fprintf(fout, "LAYER %s ACCURRENTDENSITY %s",
             layer->name(), density->type());
                          }
          */
          fprintf(fout, " FREQUENCY");
          for (j = 0; j < density->numFrequency(); j++) {
            fprintf(fout, " %g", chkNum(density->frequency(j)));
          }
          fprintf(fout, "\n");
          needHeading = 1;
        }
        if (density->numWidths()) {
          if (needHeading) {
            fprintf(fout,
                    "LAYER %s ACCURRENTDENSITY %s",
                    layer->name(),
                    density->type());
          }
          fprintf(fout, " WIDTH");
          for (j = 0; j < density->numWidths(); j++) {
            fprintf(fout, " %g", chkNum(density->width(j)));
          }
          fprintf(fout, "\n");
          needHeading = 1;
        }
        if (density->numTableEntries()) {
          if (needHeading) {
            fprintf(fout,
                    "LAYER %s ACCURRENTDENSITY %s",
                    layer->name(),
                    density->type());
            k = 0;
          } else {
            k = 5;
          }
          fprintf(fout, " TABLEENTRIES");
          for (j = 0; j < density->numTableEntries(); j++) {
            if (k > 4) {
              fprintf(fout,
                      "\nLAYER %s ACCURRENTDENSITY %s TABLEENTRIES %g",
                      layer->name(),
                      density->type(),
                      chkNum(density->tableEntry(j)));
              k = 1;
            } else {
              fprintf(fout, " %g", chkNum(density->tableEntry(j)));
              k++;
            }
          }
          fprintf(fout, "\n");
          needHeading = 1;
        }
      }
    }
  }
  if (layer->numDccurrentDensity()) {
    for (i = 0; i < layer->numDccurrentDensity(); i++) {
      density = layer->dccurrent(i);
      fprintf(
          fout, "LAYER %s DCCURRENTDENSITY %s", layer->name(), density->type());
      needHeading = 0;
      if (density->hasOneEntry()) {
        fprintf(fout, " %g\n", chkNum(density->oneEntry()));
      } else {
        if (density->numCutareas()) {
          /*
                          if (needHeading) {
                             fprintf(fout, "LAYER %s DCCURRENTDENSITY %s",
             layer->name(), density->type());
                          }
          */
          fprintf(fout, " CUTAREA");
          for (j = 0; j < density->numCutareas(); j++) {
            fprintf(fout, " %g", chkNum(density->cutArea(j)));
          }
          fprintf(fout, "\n");
          needHeading = 1;
        }
        if (density->numWidths()) {
          if (needHeading) {
            fprintf(fout,
                    "LAYER %s DCCURRENTDENSITY %s",
                    layer->name(),
                    density->type());
          }
          fprintf(fout, " WIDTH");
          for (j = 0; j < density->numWidths(); j++) {
            fprintf(fout, " %g", chkNum(density->width(j)));
          }
          fprintf(fout, "\n");
          needHeading = 1;
        }
        if (density->numTableEntries()) {
          if (needHeading) {
            fprintf(fout,
                    "LAYER %s DCCURRENTDENSITY %s",
                    layer->name(),
                    density->type());
          }
          fprintf(fout, " TABLEENTRIES");
          for (j = 0; j < density->numTableEntries(); j++) {
            fprintf(fout, " %g", chkNum(density->tableEntry(j)));
          }
          fprintf(fout, "\n");
          needHeading = 1;
        }
      }
    }
  }

  for (i = 0; i < layer->numSpacingTable(); i++) {
    spTable = layer->spacingTable(i);
    if (spTable->isInfluence()) {
      influence = spTable->influence();
      for (j = 0; j < influence->numInfluenceEntry(); j++) {
        fprintf(
            fout,
            "LAYER %s SPACINGTABLE INFLUENCE WIDTH %g WITHIN %g SPACING %g\n",
            layer->name(),
            influence->width(j),
            influence->distance(j),
            influence->spacing(j));
      }
    } else if (spTable->isParallel()) {
      parallel = spTable->parallel();
      fprintf(fout, "LAYER %s SPACINGTABLE PARALLELRUNLENGTH", layer->name());
      for (j = 0; j < parallel->numLength(); j++) {
        fprintf(fout, " %g", parallel->length(j));
      }
      fprintf(fout, "\n");
      for (j = 0; j < parallel->numWidth(); j++) {
        fprintf(fout,
                "LAYER %s SPACINGTABLE PARALLELRUNLENGTH WIDTH %g",
                layer->name(),
                parallel->width(j));
        for (k = 0; k < parallel->numLength(); k++) {
          fprintf(fout, " %g", parallel->widthSpacing(j, k));
        }
      }
      fprintf(fout, "\n");
    } else {
      twoWidths = spTable->twoWidths();
      for (j = 0; j < twoWidths->numWidth(); j++) {
        fprintf(fout, "LAYER %s TWOWIDTHS", layer->name());
        fprintf(fout, " WIDTH %g", twoWidths->width(j));
        if (twoWidths->hasWidthPRL(j)) {
          fprintf(fout, " PRL %g", twoWidths->widthPRL(j));
        }
        for (k = 0; k < twoWidths->numWidthSpacing(j); k++) {
          fprintf(fout, " %g", twoWidths->widthSpacing(j, k));
        }
        fprintf(fout, "\n");
      }
    }
  }
  propNum = layer->numProps();
  if (propNum > 0) {
    for (i = 0; i < propNum; i++) {
      fprintf(fout, "LAYER %s PROP ", layer->name());
      // value can either be a string or number
      fprintf(fout, "%s %s ", layer->propName(i), layer->propValue(i));
      pType = layer->propType(i);
      switch (pType) {
        case 'R':
          fprintf(fout, "REAL");
          break;
        case 'I':
          fprintf(fout, "INTEGER");
          break;
        case 'S':
          fprintf(fout, "STRING");
          break;
        case 'Q':
          fprintf(fout, "QUOTESTRING");
          break;
        case 'N':
          fprintf(fout, "NUMBER");
          break;
      }
      fprintf(fout, "\n");
    }
  }

  return 0;
}

// Macro
int macroCB(lefrCallbackType_e c, lefiMacro* macro, lefiUserData ud)
{
  lefiSitePattern* pattern;
  int propNum, i, hasPrtSym = 0;

  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout, "MACRO %s", macro->name());
  if (macro->hasClass()) {
    fprintf(fout, " CLASS %s", macro->macroClass());
  }
  if (macro->isFixedMask()) {
    fprintf(fout, " FIXEDMASK ");
  }
  if (macro->hasEEQ()) {
    fprintf(fout, " EEQ %s", macro->EEQ());
  }
  if (macro->hasLEQ()) {
    fprintf(fout, " LEQ %s", macro->LEQ());
  }
  if (macro->hasSource()) {
    fprintf(fout, " SOURCE %s", macro->source());
  }
  if (macro->hasXSymmetry()) {
    fprintf(fout, " SYMMETRY X ");
    hasPrtSym = 1;
  }
  if (macro->hasYSymmetry()) {  // print X Y & R90 in one line
    if (!hasPrtSym) {
      fprintf(fout, "  SYMMETRY Y ");
      hasPrtSym = 1;
    } else {
      fprintf(fout, "Y ");
    }
  }
  if (macro->has90Symmetry()) {
    if (!hasPrtSym) {
      fprintf(fout, "  SYMMETRY R90 ");
      hasPrtSym = 1;
    } else {
      fprintf(fout, "R90 ");
    }
  }
  fprintf(fout, "\n");
  if (macro->hasSiteName()) {
    fprintf(fout, "MACRO %s SITE %s\n", macro->name(), macro->siteName());
  }
  if (macro->hasSitePattern()) {
    for (i = 0; i < macro->numSitePattern(); i++) {
      pattern = macro->sitePattern(i);
      if (pattern->hasStepPattern()) {
        fprintf(fout,
                "MACRO %s SITE %s %g %g %d DO %g BY %g STEP %g %g\n",
                macro->name(),
                pattern->name(),
                chkNum(pattern->x()),
                chkNum(pattern->y()),
                pattern->orient(),
                chkNum(pattern->xStart()),
                chkNum(pattern->yStart()),
                chkNum(pattern->xStep()),
                chkNum(pattern->yStep()));
      } else {
        fprintf(fout,
                "MACRO %s SITE %s %g %g %d\n",
                macro->name(),
                pattern->name(),
                chkNum(pattern->x()),
                chkNum(pattern->y()),
                pattern->orient());
      }
    }
  }
  if (macro->hasSize()) {
    fprintf(fout,
            "MACRO %s SIZE %g BY %g\n",
            macro->name(),
            chkNum(macro->sizeX()),
            chkNum(macro->sizeY()));
  }
  if (macro->hasForeign()) {
    for (i = 0; i < macro->numForeigns(); i++) {
      fprintf(
          fout, "MACRO %s FOREIGN %s", macro->name(), macro->foreignName(i));
      if (macro->hasForeignPoint(i)) {
        fprintf(fout,
                " ( %g %g )",
                chkNum(macro->foreignX(i)),
                chkNum(macro->foreignY(i)));
        if (macro->hasForeignOrient(i)) {
          fprintf(fout, " %s", lefiOrientStr(macro->foreignOrient(i)));
        }
      }
      fprintf(fout, "\n");
    }
  }
  if (macro->hasOrigin()) {
    fprintf(fout,
            "MACRO %s ORIGIN ( %g %g )\n",
            macro->name(),
            chkNum(macro->originX()),
            chkNum(macro->originY()));
  }
  if (macro->hasPower()) {
    fprintf(fout, "MACRO %s POWER %g\n", macro->name(), chkNum(macro->power()));
  }
  propNum = macro->numProperties();
  if (propNum > 0) {
    for (i = 0; i < propNum; i++) {
      fprintf(fout, "MACRO %s PROP", macro->name());
      // value can either be a string or number
      if (macro->propValue(i)) {
        fprintf(fout, " %s %s\n", macro->propName(i), macro->propValue(i));
      } else {
        fprintf(
            fout, " %s %g\n", macro->propName(i), chkNum(macro->propNum(i)));
      }
    }
  }
  return 0;
}

// Manufacturinggrid
int manufacturingCB(lefrCallbackType_e c, double num, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout, "MANUFACTURINGGRID %g\n", chkNum(num));
  return 0;
}

// Maxviastack
int maxStackViaCB(lefrCallbackType_e c,
                  lefiMaxStackVia* maxStack,
                  lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout, "MAXVIASTACK %d", maxStack->maxStackVia());
  if (maxStack->hasMaxStackViaRange()) {
    fprintf(fout,
            " RANGE %s %s",
            maxStack->maxStackViaBottomLayer(),
            maxStack->maxStackViaTopLayer());
  }
  fprintf(fout, "\n");
  return 0;
}

// Minfeature
int minFeatureCB(lefrCallbackType_e c, lefiMinFeature* min, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout, "MINFEATURE %g %g\n", chkNum(min->one()), chkNum(min->two()));
  return 0;
}

// Universalnoisemargin
int noiseMarginCB(lefrCallbackType_e c,
                  lefiNoiseMargin* margin,
                  lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout,
          "UNIVERSALNOISEMARGIN %g %g\n",
          chkNum(margin->high),
          chkNum(margin->low));
  return 0;
}

// NoiseTable
int noiseTableCB(lefrCallbackType_e c, lefiNoiseTable* table, lefiUserData ud)
{
  int i, j, k, l;
  lefiNoiseEdge* edge;
  lefiNoiseResistance* resist;
  lefiNoiseVictim* victim;

  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout, "CROSSTALK NOISETABLE %d\n", table->num());
  if (table->numEdges() > 0) {
    for (i = 0; i < table->numEdges(); i++) {
      edge = table->edge(i);
      // resistances
      if (edge->numResistances()) {
        for (j = 0; j < edge->numResistances(); j++) {
          resist = edge->resistance(j);
          fprintf(fout,
                  "NOISETABLE %d RESISTANCE %g\n",
                  table->num(),
                  chkNum(resist->num(j)));
          // victims
          for (k = 0; k < resist->numVictims(); k++) {
            victim = resist->victim(k);
            if (victim->length() > 0) {
              fprintf(
                  fout,
                  "CROSSTALK NOISETABLE %d RESISTANCE %g VICTIMLENGTH % g\n",
                  table->num(),
                  chkNum(resist->num(j)),
                  chkNum(victim->length()));
            }
            for (l = 0; l < victim->numNoises(); l++) {
              fprintf(fout,
                      "CROSSTALK NOISETABLE %d RESISTANCE %g VICTIMLENGT %g "
                      "NOISEFACTOR %g\n",
                      table->num(),
                      chkNum(resist->num(j)),
                      chkNum(victim->length()),
                      chkNum(victim->noise(l)));
            }
          }
        }
      }
    }
  }
  return 0;
}

// Nondefault
int nonDefaultCB(lefrCallbackType_e c, lefiNonDefault* def, lefiUserData ud)
{
  int i;
  lefiVia* via;
  lefiSpacing* spacing;
  char defName[1024];

  checkType(c);
  if (ud != userData) {
    dataError();
  }
  if (def->hasHardspacing()) {
    fprintf(fout, "NONDEFAULTRULE HARDSPACING\n");
  }
  for (i = 0; i < def->numLayers(); i++) {
    fprintf(fout, "NONDEFAULTRULE %s LAYER %s", def->name(), def->layerName(i));
    if (def->hasLayerWidth(i)) {
      fprintf(fout, " WIDTH %g", chkNum(def->layerWidth(i)));
    }
    if (def->hasLayerDiagWidth(i)) {
      fprintf(fout, " DIAGWIDTH %g", chkNum(def->layerDiagWidth(i)));
    }
    if (def->hasLayerSpacing(i)) {
      fprintf(fout, " SPACING %g", chkNum(def->layerSpacing(i)));
    }
    if (def->hasLayerWireExtension(i)) {
      fprintf(fout, " WIREEXTENSION %g", chkNum(def->layerWireExtension(i)));
    }
    if (def->hasLayerResistance(i)) {
      fprintf(fout, " RESISTANCE RPERSQ %g", chkNum(def->layerResistance(i)));
    }
    if (def->hasLayerCapacitance(i)) {
      fprintf(
          fout, " CAPACITANCE CPERSQDIST %g", chkNum(def->layerCapacitance(i)));
    }
    if (def->hasLayerEdgeCap(i)) {
      fprintf(fout, " EDGECAPACITANCE %g", chkNum(def->layerEdgeCap(i)));
    }
    fprintf(fout, "\n");
  }

  sprintf(defName, "NONDEFAULTRULE %s VIA", def->name());
  // handle via in nondefaultrule
  for (i = 0; i < def->numVias(); i++) {
    via = def->viaRule(i);
    lefVia(via, defName);
  }

  sprintf(defName, "NONDEFAULTRULE %s SPACING", def->name());
  // handle spacing in nondefaultrule
  for (i = 0; i < def->numSpacingRules(); i++) {
    spacing = def->spacingRule(i);
    lefSpacing(spacing, defName);
  }

  if (def->numUseVia() > 0) {
    fprintf(fout, "NONDEFAULTRULE %s USEVIA", def->name());
    // handle spacing in nondefaultrule
    for (i = 0; i < def->numUseVia(); i++) {
      fprintf(fout, " %s", def->viaName(i));
    }
    fprintf(fout, "\n");
  }

  if (def->numUseViaRule() > 0) {
    fprintf(fout, "NONDEFAULTRULE %s USEVIARULE", def->name());
    // handle spacing in nondefaultrule
    for (i = 0; i < def->numUseViaRule(); i++) {
      fprintf(fout, " %s", def->viaRuleName(i));
    }
    fprintf(fout, "\n");
  }

  if (def->numMinCuts() > 0) {
    fprintf(fout, "NONDEFAULTRULE %s MINCUTS", def->name());
    // handle spacing in nondefaultrule
    for (i = 0; i < def->numMinCuts(); i++) {
      fprintf(fout, " %s %d", def->cutLayerName(i), def->numCuts(i));
    }
    fprintf(fout, "\n");
  }

  return 0;
}

// Nowireextension
int noWireExtCB(lefrCallbackType_e c, const char* wireExt, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout, "NOWIREEXTENSION %s\n", wireExt);
  return 0;
}

// Obstruction
int obstructionCB(lefrCallbackType_e c, lefiObstruction* obs, lefiUserData ud)
{
  lefiGeometries* geometry;

  checkType(c);
  if (ud != userData) {
    dataError();
  }
  geometry = obs->geometries();
  prtGeometry(geometry, (char*) "OBS");
  return 0;
}

// Pin
int pinCB(lefrCallbackType_e c, lefiPin* pin, lefiUserData ud)
{
  int numPorts, i, j;
  lefiGeometries* geometry;
  lefiPinAntennaModel* aModel;

  checkType(c);
  if (ud != userData) {
    dataError();
  }
  if (pin->hasForeign()) {
    if (pin->hasForeignOrient()) {
      fprintf(fout,
              "PIN %s FOREIGN %s STRUCTURE %g %g %s\n",
              pin->name(),
              pin->foreignName(),
              chkNum(pin->foreignX()),
              chkNum(pin->foreignY()),
              lefiOrientStr(pin->foreignOrient()));
    } else if (pin->hasForeignPoint()) {
      fprintf(fout,
              "PIN %s FOREIGN %s STRUCTURE %g %g",
              pin->name(),
              pin->foreignName(),
              chkNum(pin->foreignX()),
              chkNum(pin->foreignY()));
    } else {
      fprintf(fout, "PIN %s FOREIGN %s\n", pin->name(), pin->foreignName());
    }
  }
  if (pin->hasLEQ()) {
    fprintf(fout, "PIN %s LEQ %s\n", pin->name(), pin->LEQ());
  }
  if (pin->hasDirection()) {
    fprintf(fout, "PIN %s DIRECTION %s\n", pin->name(), pin->direction());
  }
  if (pin->hasUse()) {
    fprintf(fout, "PIN %s USE %s\n", pin->name(), pin->use());
  }
  if (pin->hasShape()) {
    fprintf(fout, "PIN %s SHAPE %s\n", pin->name(), pin->shape());
  }
  if (pin->hasMustjoin()) {
    fprintf(fout, "PIN %s MUSTJOIN %s\n", pin->name(), pin->mustjoin());
  }
  if (pin->hasOutMargin()) {
    fprintf(fout,
            "PIN %s OUTPUTNOISEMARGIN %g %g\n",
            pin->name(),
            chkNum(pin->outMarginHigh()),
            chkNum(pin->outMarginLow()));
  }
  if (pin->hasOutResistance()) {
    fprintf(fout,
            "PIN %s OUTPUTRESISTANCE %g %g\n",
            pin->name(),
            chkNum(pin->outResistanceHigh()),
            chkNum(pin->outResistanceLow()));
  }
  if (pin->hasInMargin()) {
    fprintf(fout,
            "PIN %s INPUTNOISEMARGIN %g %g\n",
            pin->name(),
            chkNum(pin->inMarginHigh()),
            chkNum(pin->inMarginLow()));
  }
  if (pin->hasPower()) {
    fprintf(fout, "PIN %s POWER %g\n", pin->name(), chkNum(pin->power()));
  }
  if (pin->hasLeakage()) {
    fprintf(fout, "PIN %s LEAKAGE %g\n", pin->name(), chkNum(pin->leakage()));
  }
  if (pin->hasMaxload()) {
    fprintf(fout, "PIN %s MAXLOAD %g\n", pin->name(), chkNum(pin->maxload()));
  }
  if (pin->hasCapacitance()) {
    fprintf(fout,
            "PIN %s CAPACITANCE %g\n",
            pin->name(),
            chkNum(pin->capacitance()));
  }
  if (pin->hasResistance()) {
    fprintf(
        fout, "PIN %s RESISTANCE %g\n", pin->name(), chkNum(pin->resistance()));
  }
  if (pin->hasPulldownres()) {
    fprintf(fout,
            "PIN %s PULLDOWNRES %g\n",
            pin->name(),
            chkNum(pin->pulldownres()));
  }
  if (pin->hasTieoffr()) {
    fprintf(fout, "PIN %s TIEOFFR %g\n", pin->name(), chkNum(pin->tieoffr()));
  }
  if (pin->hasVHI()) {
    fprintf(fout, "PIN %s VHI %g\n", pin->name(), chkNum(pin->VHI()));
  }
  if (pin->hasVLO()) {
    fprintf(fout, "PIN %s VLO %g\n", pin->name(), chkNum(pin->VLO()));
  }
  if (pin->hasRiseVoltage()) {
    fprintf(fout,
            "PIN %s RISEVOLTAGETHRESHOLD %g\n",
            pin->name(),
            chkNum(pin->riseVoltage()));
  }
  if (pin->hasFallVoltage()) {
    fprintf(fout,
            "PIN %s FALLVOLTAGETHRESHOLD %g\n",
            pin->name(),
            chkNum(pin->fallVoltage()));
  }
  if (pin->hasRiseThresh()) {
    fprintf(
        fout, "PIN %s RISETHRESH %g\n", pin->name(), chkNum(pin->riseThresh()));
  }
  if (pin->hasFallThresh()) {
    fprintf(
        fout, "PIN %s FALLTHRESH %g\n", pin->name(), chkNum(pin->fallThresh()));
  }
  if (pin->hasRiseSatcur()) {
    fprintf(
        fout, "PIN %s RISESATCUR %g\n", pin->name(), chkNum(pin->riseSatcur()));
  }
  if (pin->hasFallSatcur()) {
    fprintf(
        fout, "PIN %s FALLSATCUR %g\n", pin->name(), chkNum(pin->fallSatcur()));
  }
  if (pin->hasRiseSlewLimit()) {
    fprintf(fout,
            "PIN %s RISESLEWLIMIT %g\n",
            pin->name(),
            chkNum(pin->riseSlewLimit()));
  }
  if (pin->hasFallSlewLimit()) {
    fprintf(fout,
            "PIN %s FALLSLEWLIMIT %g\n",
            pin->name(),
            chkNum(pin->fallSlewLimit()));
  }
  if (pin->hasCurrentSource()) {
    fprintf(
        fout, "PIN %s CURRENTSOURCE %s\n", pin->name(), pin->currentSource());
  }
  if (pin->hasTables()) {
    fprintf(fout,
            "PIN %s IV_TABLES %s %s\n",
            pin->name(),
            pin->tableHighName(),
            pin->tableLowName());
  }
  if (pin->hasTaperRule()) {
    fprintf(fout, "PIN %s TAPERRULE %s\n", pin->name(), pin->taperRule());
  }
  if (pin->hasNetExpr()) {
    fprintf(fout, "PIN %s NETEXPR %s\n", pin->name(), pin->netExpr());
  }
  if (pin->hasSupplySensitivity()) {
    fprintf(fout,
            "PIN %s SUPPLYSENSITIVITY %s\n",
            pin->name(),
            pin->supplySensitivity());
  }
  if (pin->hasGroundSensitivity()) {
    fprintf(fout,
            "PIN %s GROUNDSENSITIVITY %s\n",
            pin->name(),
            pin->groundSensitivity());
  }
  if (pin->hasAntennaSize()) {
    for (i = 0; i < pin->numAntennaSize(); i++) {
      fprintf(
          fout, "PIN %s ANTSIZE %g", pin->name(), chkNum(pin->antennaSize(i)));
      if (pin->antennaSizeLayer(i)) {
        fprintf(fout, " LAYER %s\n", pin->antennaSizeLayer(i));
      } else {
        fprintf(fout, "\n");
      }
    }
  }
  if (pin->hasAntennaMetalArea()) {
    for (i = 0; i < pin->numAntennaMetalArea(); i++) {
      fprintf(fout,
              "PIN %s ANTMETALAREA %g",
              pin->name(),
              chkNum(pin->antennaMetalArea(i)));
      if (pin->antennaMetalAreaLayer(i)) {
        fprintf(fout, " LAYER %s\n", pin->antennaMetalAreaLayer(i));
      } else {
        fprintf(fout, "\n");
      }
    }
  }
  if (pin->hasAntennaMetalLength()) {
    for (i = 0; i < pin->numAntennaMetalLength(); i++) {
      fprintf(fout,
              "PIN %s ANTMETALLENGTH %g",
              pin->name(),
              chkNum(pin->antennaMetalLength(i)));
      if (pin->antennaMetalLengthLayer(i)) {
        fprintf(fout, " LAYER %s\n", pin->antennaMetalLengthLayer(i));
      } else {
        fprintf(fout, "\n");
      }
    }
  }
  if (pin->hasAntennaPartialMetalArea()) {
    for (i = 0; i < pin->numAntennaPartialMetalArea(); i++) {
      fprintf(fout,
              "PIN %s ANTPARTIALMETALAREA %g",
              pin->name(),
              chkNum(pin->antennaPartialMetalArea(i)));
      if (pin->antennaPartialMetalAreaLayer(i)) {
        fprintf(fout, " LAYER %s", pin->antennaPartialMetalAreaLayer(i));
      }
      fprintf(fout, "\n");
    }
  }
  if (pin->hasAntennaPartialMetalSideArea()) {
    for (i = 0; i < pin->numAntennaPartialMetalSideArea(); i++) {
      fprintf(fout,
              "PIN %s ANTPARTIALMETALSIDEAREA %g",
              pin->name(),
              chkNum(pin->antennaPartialMetalSideArea(i)));
      if (pin->antennaPartialMetalSideAreaLayer(i)) {
        fprintf(fout, " LAYER %s ", pin->antennaPartialMetalSideAreaLayer(i));
      }
      fprintf(fout, "\n");
    }
  }
  if (pin->hasAntennaPartialCutArea()) {
    for (i = 0; i < pin->numAntennaPartialCutArea(); i++) {
      fprintf(fout,
              "PIN %s ANTPARTIALCUTAREA %g",
              pin->name(),
              chkNum(pin->antennaPartialCutArea(i)));
      if (pin->antennaPartialCutAreaLayer(i)) {
        fprintf(fout, " LAYER %s ", pin->antennaPartialCutAreaLayer(i));
      }
      fprintf(fout, "\n");
    }
  }
  if (pin->hasAntennaDiffArea()) {
    for (i = 0; i < pin->numAntennaDiffArea(); i++) {
      fprintf(fout,
              "PIN %s ANTDIFFAREA %g",
              pin->name(),
              chkNum(pin->antennaDiffArea(i)));
      if (pin->antennaDiffAreaLayer(i)) {
        fprintf(fout, " LAYER %s ", pin->antennaDiffAreaLayer(i));
      }
      fprintf(fout, "\n");
    }
  }

  for (j = 0; j < pin->numAntennaModel(); j++) {
    aModel = pin->antennaModel(j);

    if (aModel->hasAntennaGateArea()) {
      for (i = 0; i < aModel->numAntennaGateArea(); i++) {
        fprintf(fout,
                "PIN %s %s ANTGATEAREA %g",
                pin->name(),
                aModel->antennaOxide(),
                chkNum(aModel->antennaGateArea(i)));
        if (aModel->antennaGateAreaLayer(i)) {
          fprintf(fout, " LAYER %s ", aModel->antennaGateAreaLayer(i));
        }
        fprintf(fout, "\n");
      }
    }
    if (aModel->hasAntennaMaxAreaCar()) {
      for (i = 0; i < aModel->numAntennaMaxAreaCar(); i++) {
        fprintf(fout,
                "PIN %s %s ANTMAXAREACAR %g",
                pin->name(),
                aModel->antennaOxide(),
                chkNum(aModel->antennaMaxAreaCar(i)));
        if (aModel->antennaMaxAreaCarLayer(i)) {
          fprintf(fout, " LAYER %s ", aModel->antennaMaxAreaCarLayer(i));
        }
        fprintf(fout, "\n");
      }
    }
    if (aModel->hasAntennaMaxSideAreaCar()) {
      for (i = 0; i < aModel->numAntennaMaxSideAreaCar(); i++) {
        fprintf(fout,
                "PIN %s %s ANTMAXAREACAR %g",
                pin->name(),
                aModel->antennaOxide(),
                chkNum(aModel->antennaMaxSideAreaCar(i)));
        if (aModel->antennaMaxSideAreaCarLayer(i)) {
          fprintf(fout, " LAYER %s ", aModel->antennaMaxSideAreaCarLayer(i));
        }
        fprintf(fout, "\n");
      }
    }
    if (aModel->hasAntennaMaxCutCar()) {
      for (i = 0; i < aModel->numAntennaMaxCutCar(); i++) {
        fprintf(fout,
                "PIN %s %s ANTMAXCUTCAR %g",
                pin->name(),
                aModel->antennaOxide(),
                chkNum(aModel->antennaMaxCutCar(i)));
        if (aModel->antennaMaxCutCarLayer(i)) {
          fprintf(fout, " LAYER %s ", aModel->antennaMaxCutCarLayer(i));
        }
        fprintf(fout, "\n");
      }
    }
  }

  if (pin->numProperties() > 0) {
    for (i = 0; i < pin->numProperties(); i++) {
      fprintf(fout, "PIN %s PROP ", pin->name());
      // value can either be a string or number
      if (pin->propValue(i)) {
        fprintf(fout, "%s %s\n", pin->propName(i), pin->propValue(i));
      } else {
        fprintf(fout, "%s %g\n", pin->propName(i), chkNum(pin->propNum(i)));
      }
    }
  }

  numPorts = pin->numPorts();

  char pinName[1024];

  for (i = 0; i < numPorts; i++) {
    sprintf(pinName, "PIN %s PORT", pin->name());
    geometry = pin->port(i);
    prtGeometry(geometry, pinName);
  }
  return 0;
}

int densityCB(lefrCallbackType_e c, lefiDensity* density, lefiUserData ud)
{
  struct lefiGeomRect rect;

  checkType(c);
  if (ud != userData) {
    dataError();
  }
  for (int i = 0; i < density->numLayer(); i++) {
    for (int j = 0; j < density->numRects(i); j++) {
      rect = density->getRect(i, j);
      fprintf(fout,
              "DENSITY LAYER %s RECT %g %g %g %g %g\n",
              density->layerName(i),
              rect.xl,
              rect.yl,
              rect.xh,
              rect.yh,
              density->densityValue(i, j));
    }
  }
  return 0;
}

// Property definition
int propDefCB(lefrCallbackType_e c, lefiProp* prop, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout, "PROPDEF %s %s", prop->propType(), prop->propName());
  if (prop->hasRange()) {
    fprintf(fout, " RANGE %g %g", chkNum(prop->left()), chkNum(prop->right()));
  }
  if (prop->hasNumber()) {
    fprintf(fout, " NUMBER %g", chkNum(prop->number()));
  }
  switch (prop->dataType()) {
    case 'I':
      fprintf(fout, " TYPE I");
      break;
    case 'R':
      fprintf(fout, " TYPE R");
      break;
    case 'S':
      fprintf(fout, " TYPE S");
      break;
    case 'Q':
      fprintf(fout, " TYPE Q");
      break;
    case 'N':
      fprintf(fout, " TYPE N");
      break;
  }
  fprintf(fout, "\n");
  return 0;
}

// Site
int siteCB(lefrCallbackType_e c, lefiSite* site, lefiUserData ud)
{
  int hasPrtSym = 0;
  int i;

  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout, "SITE %s", site->name());
  if (site->hasClass()) {
    fprintf(fout, " CLASS %s", site->siteClass());
  }
  if (site->hasXSymmetry()) {
    fprintf(fout, " SYMMETRY X ");
    hasPrtSym = 1;
  }
  if (site->hasYSymmetry()) {
    if (hasPrtSym) {
      fprintf(fout, "Y ");
    } else {
      fprintf(fout, " SYMMETRY Y ");
      hasPrtSym = 1;
    }
  }
  if (site->has90Symmetry()) {
    if (hasPrtSym) {
      fprintf(fout, "R90 ");
    } else {
      fprintf(fout, " SYMMETRY R90 ");
      hasPrtSym = 1;
    }
  }
  if (site->hasSize()) {
    fprintf(
        fout, " SIZE %g BY %g", chkNum(site->sizeX()), chkNum(site->sizeY()));
  }
  fprintf(fout, "\n");
  if (site->hasRowPattern()) {  // 5.6, a line for ea rowpattern for sorting
    for (i = 0; i < site->numSites(); i++) {
      fprintf(fout,
              "SITE %s ROWPATTERN %s %s\n",
              site->name(),
              site->siteName(i),
              site->siteOrientStr(i));
    }
  }

  return 0;
}

// Spacing
int spacingCB(lefrCallbackType_e c, lefiSpacing* spacing, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  lefSpacing(spacing, (char*) "SPACING");
  return 0;
}

// Timing
int timingCB(lefrCallbackType_e c, lefiTiming* timing, lefiUserData ud)
{
  int i;
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  for (i = 0; i < timing->numFromPins(); i++) {
    fprintf(fout, "TIMING FROMPIN %s\n", timing->fromPin(i));
  }
  for (i = 0; i < timing->numToPins(); i++) {
    fprintf(fout, "TIMING TOPIN %s\n", timing->toPin(i));
  }
  fprintf(fout,
          "TIMING RISE SLEW1 %g %g %g %g\n",
          chkNum(timing->riseSlewOne()),
          chkNum(timing->riseSlewTwo()),
          chkNum(timing->riseSlewThree()),
          chkNum(timing->riseSlewFour()));
  if (timing->hasRiseSlew2()) {
    fprintf(fout,
            "TIMING RISE SLEW2 %g %g %g\n",
            chkNum(timing->riseSlewFive()),
            chkNum(timing->riseSlewSix()),
            chkNum(timing->riseSlewSeven()));
  }
  if (timing->hasFallSlew()) {
    fprintf(fout,
            "TIMING FALL SLEW1 %g %g %g %g\n",
            chkNum(timing->fallSlewOne()),
            chkNum(timing->fallSlewTwo()),
            chkNum(timing->fallSlewThree()),
            chkNum(timing->fallSlewFour()));
  }
  if (timing->hasFallSlew2()) {
    fprintf(fout,
            "TIMING FALL SLEW2 %g %g %g\n",
            chkNum(timing->fallSlewFive()),
            chkNum(timing->fallSlewSix()),
            chkNum(timing->riseSlewSeven()));
  }
  if (timing->hasRiseIntrinsic()) {
    fprintf(fout,
            "TIMING RISE INTRINSIC %g %g\n",
            chkNum(timing->riseIntrinsicOne()),
            chkNum(timing->riseIntrinsicTwo()));
    fprintf(fout,
            "TIMING RISE VARIABLE %g %g\n",
            chkNum(timing->riseIntrinsicThree()),
            chkNum(timing->riseIntrinsicFour()));
  }
  if (timing->hasFallIntrinsic()) {
    fprintf(fout,
            "TIMING FALL INTRINSIC %g %g\n",
            chkNum(timing->fallIntrinsicOne()),
            chkNum(timing->fallIntrinsicTwo()));
    fprintf(fout,
            "TIMING RISE VARIABLE %g %g\n",
            chkNum(timing->fallIntrinsicThree()),
            chkNum(timing->fallIntrinsicFour()));
  }
  if (timing->hasRiseRS()) {
    fprintf(fout,
            "TIMING RISERS %g %g\n",
            chkNum(timing->riseRSOne()),
            chkNum(timing->riseRSTwo()));
  }
  if (timing->hasRiseCS()) {
    fprintf(fout,
            "TIMING RISECS %g %g\n",
            chkNum(timing->riseCSOne()),
            chkNum(timing->riseCSTwo()));
  }
  if (timing->hasFallRS()) {
    fprintf(fout,
            "TIMING FALLRS %g %g\n",
            chkNum(timing->fallRSOne()),
            chkNum(timing->fallRSTwo()));
  }
  if (timing->hasFallCS()) {
    fprintf(fout,
            "TIMING FALLCS %g %g\n",
            chkNum(timing->fallCSOne()),
            chkNum(timing->fallCSTwo()));
  }
  if (timing->hasUnateness()) {
    fprintf(fout, "TIMING UNATENESS %s\n", timing->unateness());
  }
  if (timing->hasRiseAtt1()) {
    fprintf(fout,
            "TIMING RISESATT1 %g %g\n",
            chkNum(timing->riseAtt1One()),
            chkNum(timing->riseAtt1Two()));
  }
  if (timing->hasFallAtt1()) {
    fprintf(fout,
            "TIMING FALLSATT1 %g %g\n",
            chkNum(timing->fallAtt1One()),
            chkNum(timing->fallAtt1Two()));
  }
  if (timing->hasRiseTo()) {
    fprintf(fout,
            "TIMING RISET0 %g %g\n",
            chkNum(timing->riseToOne()),
            chkNum(timing->riseToTwo()));
  }
  if (timing->hasFallTo()) {
    fprintf(fout,
            "TIMING FALLT0 %g %g\n",
            chkNum(timing->fallToOne()),
            chkNum(timing->fallToTwo()));
  }
  return 0;
}

// Units
int unitsCB(lefrCallbackType_e c, lefiUnits* unit, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  if (unit->hasDatabase()) {
    fprintf(fout,
            "UNITS DATABASE %s %g\n",
            unit->databaseName(),
            chkNum(unit->databaseNumber()));
  }
  if (unit->hasCapacitance()) {
    fprintf(
        fout, "UNITS CAPACITANCE PICOFARADS %g\n", chkNum(unit->capacitance()));
  }
  if (unit->hasResistance()) {
    fprintf(fout, "UNITS RESISTANCE OHMS %g\n", chkNum(unit->resistance()));
  }
  if (unit->hasPower()) {
    fprintf(fout, "UNITS POWER MILLIWATTS %g\n", chkNum(unit->power()));
  }
  if (unit->hasCurrent()) {
    fprintf(fout, "UNITS CURRENT MILLIAMPS %g\n", chkNum(unit->current()));
  }
  if (unit->hasVoltage()) {
    fprintf(fout, "UNITS VOLTAGE VOLTS %g\n", chkNum(unit->voltage()));
  }
  if (unit->hasFrequency()) {
    fprintf(fout, "UNITS FREQUENCY MEGAHERTZ %g\n", chkNum(unit->frequency()));
  }
  return 0;
}

// UseMinSpacing
int useMinSpacingCB(lefrCallbackType_e c,
                    lefiUseMinSpacing* spacing,
                    lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout, "USEMINSPACING %s ", spacing->name());
  if (spacing->value()) {
    fprintf(fout, "USEMINSPACING ON\n");
  } else {
    fprintf(fout, "USEMINSPACING OFF\n");
  }
  return 0;
}

// Version
int versionCB(lefrCallbackType_e c, double num, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout, "VERSION %g\n", num);
  return 0;
}

// Via
int viaCB(lefrCallbackType_e c, lefiVia* via, lefiUserData ud)
{
  checkType(c);
  if (ud != userData) {
    dataError();
  }
  lefVia(via, (char*) "VIA");
  return 0;
}

// Viarule
int viaRuleCB(lefrCallbackType_e c, lefiViaRule* viaRule, lefiUserData ud)
{
  int numLayers, numVias, i;
  lefiViaRuleLayer* vLayer;

  checkType(c);
  if (ud != userData) {
    dataError();
  }
  fprintf(fout, "VIARULE %s", viaRule->name());
  if (viaRule->hasGenerate()) {
    fprintf(fout, " GENERATE");
  }
  if (viaRule->hasDefault()) {
    fprintf(fout, " DEFAULT");
  }
  fprintf(fout, "\n");

  numLayers = viaRule->numLayers();
  // if numLayers == 2, it is VIARULE without GENERATE and has via name
  // if numLayers == 3, it is VIARULE with GENERATE, and the 3rd layer is cut
  for (i = 0; i < numLayers; i++) {
    vLayer = viaRule->layer(i);
    lefViaRuleLayer(vLayer, viaRule->name());
  }

  if (numLayers == 2) {  // should have vianames
    numVias = viaRule->numVias();
    if (numVias == 0) {
      fprintf(fout, "Should have via names in VIARULE.\n");
    } else {
      for (i = 0; i < numVias; i++) {
        fprintf(fout,
                "VIARULE %s  VIA %s ;\n",
                viaRule->name(),
                viaRule->viaName(i));
      }
    }
  }
  if (viaRule->numProps() > 0) {
    for (i = 0; i < viaRule->numProps(); i++) {
      fprintf(
          fout, "VIARULE %s PROP %s ", viaRule->name(), viaRule->propName(i));
      if (viaRule->propValue(i)) {
        fprintf(fout, "%s ", viaRule->propValue(i));
      }
      switch (viaRule->propType(i)) {
        case 'R':
          fprintf(fout, "REAL ");
          break;
        case 'I':
          fprintf(fout, "INTEGER ");
          break;
        case 'S':
          fprintf(fout, "STRING ");
          break;
        case 'Q':
          fprintf(fout, "QUOTESTRING ");
          break;
        case 'N':
          fprintf(fout, "NUMBER ");
          break;
          fprintf(fout, "\n");
      }
    }
  }
  return 0;
}

//========

int diffLefReadFile(char* inFile, char* outFile)
{
  userData = (void*) 0x01020304;
  lefrInit();

  // Fix of CCR 758312
  lefrSetRelaxMode();

  lefrSetAntennaInputCbk(antennaCB);
  lefrSetAntennaInoutCbk(antennaCB);
  lefrSetAntennaOutputCbk(antennaCB);
  lefrSetArrayCbk(arrayCB);
  lefrSetBusBitCharsCbk(busBitCharsCB);
  lefrSetCaseSensitiveCbk(caseSensCB);
  lefrSetFixedMaskCbk(fixedMaskCB);
  lefrSetClearanceMeasureCbk(clearanceCB);
  lefrSetCorrectionTableCbk(correctTableCB);
  lefrSetDensityCbk(densityCB);
  lefrSetDielectricCbk(dielectricCB);
  lefrSetDividerCharCbk(dividerCB);
  lefrSetEdgeRateScaleFactorCbk(edgeRateCB);
  lefrSetEdgeRateThreshold1Cbk(edgeRate1CB);
  lefrSetEdgeRateThreshold2Cbk(edgeRate2CB);
  lefrSetInputAntennaCbk(inputAntCB);
  lefrSetOutputAntennaCbk(outputAntCB);
  lefrSetInoutAntennaCbk(inoutAntCB);
  lefrSetIRDropCbk(irdropCB);
  lefrSetLayerCbk(layerCB);
  lefrSetMacroCbk(macroCB);
  lefrSetManufacturingCbk(manufacturingCB);
  lefrSetMaxStackViaCbk(maxStackViaCB);
  lefrSetMinFeatureCbk(minFeatureCB);
  lefrSetNoiseMarginCbk(noiseMarginCB);
  lefrSetNoiseTableCbk(noiseTableCB);
  lefrSetNonDefaultCbk(nonDefaultCB);
  lefrSetNoWireExtensionCbk(noWireExtCB);
  lefrSetObstructionCbk(obstructionCB);
  lefrSetPinCbk(pinCB);
  lefrSetPropCbk(propDefCB);
  lefrSetSiteCbk(siteCB);
  lefrSetSpacingCbk(spacingCB);
  lefrSetTimingCbk(timingCB);
  lefrSetUnitsCbk(unitsCB);
  lefrSetUseMinSpacingCbk(useMinSpacingCB);
  lefrSetUserData((void*) 3);
  lefrSetVersionCbk(versionCB);
  lefrSetViaCbk(viaCB);
  lefrSetViaRuleCbk(viaRuleCB);

  FILE* f;
  if ((f = fopen(inFile, "r")) == nullptr) {
    fprintf(stderr, "Couldn't open input file '%s'\n", inFile);
    return (2);
  }

  if ((fout = fopen(outFile, "w")) == nullptr) {
    fprintf(stderr, "Couldn't open output file '%s'\n", outFile);
    fclose(f);
    return (2);
  }

  const int res = lefrRead(f, inFile, userData);

  fclose(f);
  fclose(fout);

  return res;
}
